package ualrcdg.server;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

import ualrcdg.shared.exceptions.CDGException;
import ualrcdg.shared.requests.AuthenticatePlayerRequest;
import ualrcdg.shared.requests.RegisterPlayerRequest;

/**
 * Maintains a list of registered Players, adds Players to that list, and authenticates Players based on the credentials stored in that list.
 */
public class PlayerRegistrar
{
	private final String defaultPlayerListing = "players.txt";
	
	private final String playerListing;
	private final HashMap< String, Player > players;
	
	/**
	 * Primary constructor
	 *  
	 * @param rootPath the name of the folder (relative to the current working directory) that contains all files and folders necessary for the CDG Server application
	 * @param playerListing the name of the file (within the rootFolder) that contains the serialized descriptions of all registered players
	 * @throws IOException If an error occurs while deserializing Player objects
	 * @throws ClassNotFoundException If the Player object implementation cannot be found
	 * @throws CDGException If a deserialized Player cannot be registered
	 */
	//LATER Consider changing rootPath parameter to Path object
	//LATER Handling of rootPath parameter is incorrect
	public PlayerRegistrar( String rootPath, String playerListing ) throws IOException, ClassNotFoundException, CDGException
	{
		if( rootPath == null )
		{
			throw new NullPointerException( "Unable to create PlayerRegistrar. root must not be null." );
		}
	
		System.out.println( "PlayeRegistrar(" + rootPath + ", " + playerListing + ")" );
		
		players = new HashMap< String, Player >( );
		
		if( playerListing != null )
		{
			this.playerListing = rootPath + playerListing;
			try
			{
				ObjectInputStream ois = new ObjectInputStream( new FileInputStream( new File( this.playerListing ) ) );

				boolean done = false;
				while( !done )
				{
					try
					{
						Player p = ( Player ) ois.readObject( );
						registerPlayer( p );
					}
					catch( EOFException e )
					{
						done = true;
					}
				}

				ois.close( );
			}
			catch( EOFException e )
			{

			}
		}
		else
		{
			this.playerListing = rootPath + defaultPlayerListing;
		}
	}

	/**
	 * Serializes all registered players to the playerListing file established in the constructor
	 * 
	 * @throws FileNotFoundException If the playerListing file cannot be located
	 * @throws IOException If an IO error occurs while serializing the registered Player objects
	 */
	public void commit( ) throws FileNotFoundException, IOException
	{
		ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( new File( playerListing ) ) );

		for( Map.Entry< String, Player > p : players.entrySet( ) )
		{
			oos.writeObject( p.getValue( ) );
			System.out.println( "PlayerRegistrar: committed " + p );
		}
		
		oos.close( );
	}
	
	/**
	 * Attempts to register a Player object with this PlayerRegistrar instance
	 * 
	 * @param p the Player to be registered
	 * @throws CDGException If the username of the Player parameter is already assigned to a registered Player
	 */
	private synchronized void registerPlayer( Player p ) throws CDGException
	{
		if( players.containsKey( p.username ) )
		{
			throw new CDGException( "PlayerRegistrar: unable to register player with username: " + p.username + ". Username is already assigned." );
		}

		players.put( p.username, p );
		
		System.out.println( "PlayerRegistrar: created mapping: " + p.username + " -> " + p.toString( ) );
	}
	
	/**
	 * Convenience method for registering a Player described in a {@link RegisterPlayerRequest} object
	 * 
	 * @param request the RegisterPlayerRequest object that contains the registration information of the Player to be registered
	 * @return true if the registration succeeds, false otherwise
	 */
	public synchronized boolean registerPlayer( RegisterPlayerRequest request )
	{
		try
		{
			registerPlayer( new Player( request.username, request.password )  );
			return true;
		}
		catch( CDGException e )
		{
			return false;
		}
	}
	
	/**
	 * Attempts to authenticate a player whose credentials are provided in a {@link AuthenticatePlayerRequest} object
	 * 
	 * @param request the AuthenticatePlayerRequest object containing the credentials of the player to be authenticated
	 * @return true if the authentication was successful, false otherwise
	 */
	public boolean authenticatePlayer( AuthenticatePlayerRequest request )
	{
		Player p = players.get( request.username );
		if( p == null )
		{
			System.out.println( "PlayerRegistrar: unable to authenticate player with username: " + request.username + ". Player does not exist." );
			return false;
		}
		else if( !p.password.equals( request.password ) )
		{
			System.out.println( "PlayerRegistrar: unable to authenticate player with username: " + request.username + " and password: " + request.password + ". Credentials do not match expected: " + p.toString( ) + "." );
			return false;
		}

		return true;
	}
}
